{

  Module is a part of project http://code.google.com/p/drivers-training-request
  Copyright (C) 2012 Leonid Vlasov leoniv@bk.ru

  This source is free software; you can redistribute it and/or modify it under
  the terms of the GNU General Public License as published by the Free
  Software Foundation; either version 2 of the License, or (at your option)
  any later version.

  This code is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  details.

  A copy of the GNU General Public License is available on the World Wide Web
  at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
  to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  MA 02111-1307, USA.
}


function _ConvArrayColToGridCol(Grid:TStringGrid;Col:Integer):Integer;
begin
  Result:=Col+Grid.FixedCols;
end;

function _ConvArrayRowToGridRow(Grid:TStringGrid;Row:Integer):Integer;
begin
  Result:=Row+Grid.FixedRows;
end;

function _ConvGridColToArrayCol(Grid:TStringGrid;Col:Integer):Integer;
begin
  Result:=Col-Grid.FixedCols;
end;

function _ConvGridRowToArrayRow(Grid:TStringGrid;Row:Integer):Integer;
begin
  Result:=Row-Grid.FixedRows;
end;


function _CountGridDataCols(Grid:TStringGrid):Integer;
begin
  Result:=Grid.ColCount-Grid.FixedCols;
end;

function _CountGridDataRow(Grid:TStringGrid):Integer;
begin
  Result:=Grid.RowCount-Grid.FixedRows;
end;

{ TDataTableRow }

function TDataTableRow.GetFieldCOunt: Integer;
begin

end;

function TDataTableRow.GetValueByName(FieldName: String): String;
begin
  Result:=FDataTAble.Cells[FDataTAble.ColIndex(FieldName),FRowIndex];
end;

function TDataTableRow.GetValueByIndex(Index: Integer): String;
begin
 Result:=FDataTAble.Cells[Index,FRowIndex];
end;

procedure TDataTableRow.SetValueByIndex(ColIndex: Integer; AValue: String);
begin
 FDataTAble.Cells[ColIndex,FRowIndex]:=AValue;
end;

procedure TDataTableRow.SetValueByName(FieldName: String; AValue: String);
var
  ColIndex:Integer;
begin
  ColIndex:=FDataTAble.ColIndex(FieldName);
  FDataTAble.Cells[ColIndex,FRowIndex]:=AValue;
end;

function TDataTableRow.GetContext(Const FormatStr,ToFrom,ColName,Message:String): String;
begin
  Result:=Format(FormatStr,[ToFrom,ColName,FRowIndex,Message]);
end;



constructor TDataTableRow.Create(ADataTAble: TDataTable; ARowIndex: Integer);
begin
  FRowIndex:=ARowIndex;
  FDataTAble:=ADataTAble;
end;

procedure TDataTableRow.Delete;
begin
  FDataTAble.DelRow(FRowIndex);
end;

function TDataTableRow.Validate(var MessArrat: TMessagesArray): Boolean;
begin
  Raise Exception.Create('Metod <TDataTableRow.Validate> Not Emplemet Yet');
  {TODO  -o: -c: : Реализовать метод TDataTableRow.Validate }
end;

function TDataTableRow.LogicalValidate(Cause: String): Boolean;
begin
  Raise Exception.Create('Metod <TDataTableRow.LogicalValidate> Not Emplemet Yet');
  {TODO  -o: -c: : Реализовать метод TDataTableRow.LogicalValidate }
end;

function _tablerow():String;
begin
  Result:='tablerow';
end;



function TDataTableRow.ToXMLString: String;
var
  i: Integer;
begin
  Result:='<'+_tablerow()+'index="'+IntToStr(FRowIndex)+'">';
  for i:=0 to FDataTAble.ColCount-1 do
     begin
       try
          Result:=Result+FDataTAble.FieldDescriptorByIndex[i].ToXMLString(ValueByIndex[i]);
       except
         on E:Exception do Raise EDataTableErr.Create(fstrDataTabtleRowExportNative,GetContext(fstrDataTabtleRowExportConext,'XmlString',FDataTAble.FieldDescriptorByIndex[i].FName,E.Message),E);
       end;
     end;
  Result:=Result+'</'+_tablerow()+'>';
end;

function TDataTableRow.ToXMLNode(const XMLDoc: TXMLDocument): TDOMElement;
var
  i:integer;
begin
 Result:=XMLDoc.CreateElement(_tablerow());
 Result.SetAttribute('index',IntToStr(FRowIndex));
 for i:=0 to FDataTAble.ColCount-1 do
    begin
      try
         Result.AppendChild(FDataTAble.FieldDescriptorByIndex[i].ToXMLNode(XMLDoc,ValueByIndex[i]));
      except
        on E:Exception do Raise EDataTableErr.Create(fstrDataTabtleRowExportNative,GetContext(fstrDataTabtleRowExportConext,'XmlNode',FDataTAble.FieldDescriptorByIndex[i].FName,E.Message),E);
      end;
    end;
end;

function TDataTableRow.ToSQLString: String;
var
  i: Integer;
begin
 Result:='(';
 for i:=0 to FDataTAble.ColCount-1 do
    begin
      try
         Result:=Result+FDataTAble.FieldDescriptorByIndex[i].ToSQLString(ValueByIndex[i]);
      except
        on E:Exception do Raise EDataTableErr.Create(fstrDataTabtleRowExportNative,GetContext(fstrDataTabtleRowExportConext,'SQLString',FDataTAble.FieldDescriptorByIndex[i].FName,E.Message),E);
      end;
      if i<FDataTAble.ColCount-1 then
      Result:=Result+',';
    end;
 Result:=Result+')';
end;

procedure TDataTableRow.FromXMLNode(Node: TDOMElement);
var
  FieldName:String;
  i: Integer;
begin
 if Node.TagName<>_tablerow() then Raise Exception.Create('Error tag name for TableRow. Expect ('+_tablerow()+') found ('+Node.TagName+')');
 for i:=0 to Node.ChildNodes.Count-1 do
   try
    ValueByName[Node.ChildNodes.Item[i].LocalName]:=FDataTAble.FieldDescriptorByIndex[i].FromXMLNode(TDOMElement(Node.ChildNodes.Item[i]));
   except
     on E:Exception do Raise EDataTableErr.Create(fstrDataTabtleRowImportNative,GetContext(fstrDataTabtleRowImportConext,'XmlNode',FDataTAble.FieldDescriptorByIndex[i].FName,E.Message),E);
   end;
end;

procedure TDataTableRow.FromSQLFildVAlue(const SQLVAlue: TDataSet);
var
  i: Integer;
begin
 for i:=0 to SQLVAlue.FieldCount-1 do
   begin
     try
         ValueByName[SQLVAlue.Fields[i].Name]:=FDataTAble.FieldDescriptorByIndex[i].FromSQLFildVAlue(SQLVAlue.Fields[i]);
     except
       on E:Exception do Raise EDataTableErr.Create(fstrDataTabtleRowImportNative,GetContext(fstrDataTabtleRowImportConext,'SQLFildValue',FDataTAble.FieldDescriptorByIndex[i].FName,E.Message),E);
     end;
   end;
end;



{ TColumnsList }

function TColumnsList.Add(Item: Pointer): Integer;
var
  i: Integer;
begin
 //
 for i:=0 to Count-1 do
    if TFieldDescriptor(Items[i]).Name = TFieldDescriptor(Item).Name
    then Raise EDynArray.Create('Duplicate name of field');
 inherited Add(Item);
end;

function TColumnsList.Index(Name: String): Integer;
var
  i: Integer;
begin
  for i:=0 to Count-1 do
     begin
       if UTF8UpperCase(Name)=UTF8UpperCase(TFieldDescriptor(Items[i]).Name) then
         begin
           Result:=i;
           Exit;
         end;
     end;
  Raise EDynArray.Create('Columt <'+Name+'> not found');
end;

function TColumnsList.Name(AIndex: Integer): string;
begin
  if (Aindex<0) or (AIndex>Count-1) then
          Raise EDynArray.Create('Index ('+IntToStr(AIndex)+') out of range');

  Result:=TFieldDescriptor(Items[AIndex]).Name;
end;


{ TDataTable }

procedure TDataTable.SetLength(Cols, Rows: Integer);
var
  i:Integer;
begin
  //Если уменьшаем надо вызвать обработчик удаления
  if ColCount>Cols then
    //Уменьшаем размер с верху вниз
     for i:=(ColCount-1)*-1 to Cols*-1 do
       begin
         if FColumns.Count-1 >= i*-1 then
             begin
               if FColumns[i*-1]<>nil then
                  TFieldDescriptor(FColumns[i*-1]).Free;
                FColumns.Delete(i*-1);
             end;
         If Assigned(FOnDelCol) then FOnDelCol(Self,i*-1);
       end;
  if RowCount>Rows then
     for i:=(RowCount-1)*-1 to Rows*-1 do
       If Assigned(FOnDelRow) then FOnDelRow(Self,i*-1);
  //Увеличили размер  вызвать обработчик добавления
  if ColCount<Cols then
       for i:=ColCount to Cols-1 do
         begin
           //Колноки добавляем по одной прежде надо добавить описание колонки в FColumns
           if FColumns.Count-1 < i then Raise EDynArray.Create('Cant find Column');
           If Assigned(FOnAddCol) then FOnAddCol(Self,i,FColumns[i]);
         end;
  if RowCount<Rows then
      for i:=RowCount to Rows-1 do
         If Assigned(FOnAddRow) then FOnAddRow(Self,i);
  FArray.SetLength(Cols, Rows);
end;

constructor TDataTable.Create;
begin
  FColumns:=TColumnsList.Create;
  //Connect array events hendlers
  FOnAddCol:=@OnArrAddCol;
  FOnAddRow:=@OnArrAddRow;
  FOnDelCol:=@OnArrDelCol;
  FOnDelRow:=@OnArrDelRow;
  FOnMoveColRow:=@OnArrColRowMoved;
  FOnSetValue:=@OnArrSetValue;
  inherited Create;
end;

procedure TDataTable.DeleteColRow(IsColumn: Boolean; Index: Integer);
begin
  inherited DeleteColRow(IsColumn, Index);
end;

function TDataTable.GetFD(AIndex: Integer): TFieldDescriptor;
begin
  Result:=TFieldDescriptor(FColumns[AIndex]);
end;

function TDataTable.GetFD(ColName: String): TFieldDescriptor;
begin
 Result:=TFieldDescriptor(FColumns[FColumns.Index(ColName)]);
end;

function TDataTable.GetGrid(GridPattern: TGridTemplate): TStringGrid;
begin
  Result:=FGrid;
end;

function TDataTable.GetRow(AIndex: Integer): TDataTableRow;
begin
  Result:=TDataTableRow.Create(Self,AIndex);
end;

function TDataTable.GetString(Col, Row: Integer): String;
begin
  Result:= Inherited GetString(Col,Row);
end;


function TDataTable.GetColCount: Integer;
begin
  inherited GetColCount;
end;

procedure TDataTable.OnArrSetValue(Sender: TObject; ACol, ARow: integer;
  ItemVAlue: String);
begin
 if FGrid = Nil then exit;
 FGrid.Cells[_ConvArrayColToGridCol(FGrid,ACol),_ConvArrayRowToGridRow(FGrid,ARow)]:=ItemValue;
end;

procedure TDataTable.OnArrAddRow(Sender: TObject; Index: Integer);
begin
 if FGrid = Nil then exit;
 FGrid.RowCount:=FGrid.RowCount+1;
end;

procedure TDataTable.OnArrAddCol(Sender: TObject; Index: Integer;
  ColDesc: Pointer);
var
  NewColumn:TGridColumn;
begin
 if FGrid = Nil then exit;
 NewColumn:=FGrid.Columns.Add;
 NewColumn.Index:=Index;
 if TFieldDescriptor(ColDesc).Caption='' then
     NewColumn.Title.Caption:=TFieldDescriptor(ColDesc).Name
 else
     NewColumn.Title.Caption:=TFieldDescriptor(ColDesc).Caption;
 NewColumn.Visible:=TFieldDescriptor(ColDesc).Visible;
 NewColumn.Width:=TFieldDescriptor(ColDesc).Width;
 NewColumn.PickList.Assign(TFieldDescriptor(ColDesc).PicList);
 NewColumn.Title.Font.Style:=[fsBold];
 NewColumn.Title.Alignment:=taCenter;
{TODO  -o: -c: : Реализовать метод TDataTable.OnArrAddCol }
end;

procedure TDataTable.OnArrDelRow(Sender: TObject; Index: Integer);
begin
 if FGrid = Nil then exit;
    FGrid.DeleteColRow(False,_ConvArrayRowToGridRow(FGrid,Index));
end;

procedure TDataTable.OnArrDelCol(Sender: TObject; Index: Integer);
begin
 if FGrid = Nil then exit;
 FGrid.DeleteColRow(True,_ConvArrayColToGridCol(FGrid,Index));
end;

procedure TDataTable.OnArrColRowMoved(Sender: TObject; IsColumn: Boolean;
  sIndex, tIndex: Integer);
Var
  OnGridColRowMover:TgridOperationEvent;
  FromIndex,ToIndex:Integer;
begin
 //проблема в зацикливании событий Массив.ПеремещениеКолонки <-> Грид.ПеремещениеКолонки
 //Надо отменить инсерт колонки в Массив не в итераном режиме порядок колонок в Массиве не важен
// а пользователю надо дать такую возможность
 if FGrid<>nil then
    begin
      OnGridColRowMover:=FGrid.OnColRowMoved;
      FGrid.OnColRowMoved:=Nil;
      if IsColumn then
         begin
           FromIndex:=_ConvArrayColToGridCol(FGrid,sIndex);
           ToIndex:=  _ConvArrayColToGridCol(FGrid,tIndex);
         end
        else
          begin
           FromIndex:=_ConvArrayRowToGridRow(FGrid,sIndex);
           ToIndex:=  _ConvArrayRowToGridRow(FGrid,tIndex);
          end ;
       Fgrid.MoveColRow(IsColumn,FromIndex,ToIndex);
       FGrid.OnColRowMoved:=OnGridColRowMover;
    end;
   //Raise EDynArray.Create('Cant insert Collumn in Array whith assigned FGreed');
end;

procedure TDataTable.OnGridColRowMoved(Sender: TObject; IsColumn: Boolean;
  sIndex, tIndex: Integer);
Var
  FromIndex,ToIndex:Integer;
begin
 if IsColumn then
    begin
      FromIndex:=_ConvGridColToArrayCol(FGrid,sIndex);
      ToIndex:=_ConvGridColToArrayCol(FGrid,tIndex);
    end
   else
     begin
      FromIndex:=_ConvGridRowToArrayRow(FGrid,sIndex);
      ToIndex:=_ConvGridRowToArrayRow(FGrid,tIndex);
     end;
 MoveColRow(IsColumn,FromIndex,ToIndex,True);
 //Call old
 if Assigned(FOnGridColRowMovedOld) then FOnGridColRowMovedOld(Sender,IsColumn,sIndex, tIndex);
end;

procedure TDataTable.OnGridEditingDone(Sender: TObject);
var
  Col,Row:integer;
begin
 Col:=_ConvGridColToArrayCol(TStringGrid(Sender),TStringGrid(Sender).Col);
 Row:=_ConvGridRowToArrayRow(TStringGrid(Sender),TStringGrid(Sender).Row);
 Cells[Col,Row]:=TStringGrid(Sender).Cells[TStringGrid(Sender).Col,TStringGrid(Sender).Row];
 //Call old
 if Assigned(FOnGridEditingDoneOld) then FOnGridEditingDoneOld(Sender);
end;

procedure TDataTable.OnGridGetEditMask(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
  GetEditMask(_ConvGridColToArrayCol(TStringGrid(Sender),ACol),Value);
 //Call old
  if Assigned(FOnGridGetEditMaskOld) then FOnGridGetEditMaskOld(Sender,ACol,ARow,Value);
end;

procedure TDataTable.OnGridGetEditText(Sender: TObject; ACol, ARow: Integer;
  var Value: string);
begin
 {TODO  -o: -c: : Здесь надо проверять значение на соответствие мавски }

 //Call old
  if Assigned(FOnGridGetEditTextOld) then FOnGridGetEditTextOld(Sender,ACol,ARow,Value);
end;

procedure TDataTable.OnGridKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  {TODO  -o: -c: : Здесь обрабатываем клавиши + строка - строка итп }

 //Call old
  if Assigned(FOnGridKeyDown) then FOnGridKeyDown(Sender,Key,Shift);
end;

procedure TDataTable.SetGrid(GridPattern: TGridTemplate; AValue: TStringGrid);
begin
  {TODO  -o: -c: : Реализовать метод TDataTable.SetGrid }
  FGrid:=AValue;
  FGrid.Clear;
  FOnGridColRowMovedOld:=FGrid.OnColRowMoved;
  FOnGridEditingDoneOld:=FGrid.OnEditingDone;
  FOnGridGetEditMaskOld:=FGrid.OnGetEditMask;
  FOnGridGetEditTextOld:=FGrid.OnGetEditText;
  FOnGridKeyDown:=FGrid.OnKeyDown;
  FGrid.OnColRowMoved:=@OnGridColRowMoved;
  FGrid.OnEditingDone:=@OnGridEditingDone;
  FGrid.OnGetEditMask:=@OnGridGetEditMask;
  FGrid.OnGetEditText:=@OnGridGetEditText;
  FGrid.OnKeyDown:=@OnGridKeyDown;

  if GridPattern<>Nil then
    begin
      FGrid.ColCount:=GridPattern.FixedCols;
      FGrid.FixedCols:=GridPattern.FixedCols;
      FGrid.RowCount:=GridPattern.FixedRows;
      FGrid.FixedRows:=GridPattern.FixedRows;
      FGrid.Options:=GridPattern.Options;
    end;

  if ColCount>0 then
    Raise Exception.Create('Metod <TDataTable.SetGrid> Not Emplemet Yet');
    {TODO  -o: -c: : Реализовать метод TDataTable.SetGrid }
  if RowCount>0 then
    Raise Exception.Create('Metod <TDataTable.SetGrid> Not Emplemet Yet');
    {TODO  -o: -c: : Реализовать метод TDataTable.SetGrid }
end;

procedure TDataTable.SetString(Col, Row: Integer; AValue: String);
begin
 FieldDescriptorByIndex[Col].Normalize(AValue);
 inherited Cells[col,row]:=AValue;
end;

//procedure TDataTable.SetOnSetValue(AValue: TOnNotifyStringItem);
//begin
//  if FOnSetValue=AValue then Exit;
//  FOnSetValue:=AValue;
//end;

procedure TDataTable.AddCol(Fdesc: TFieldDescriptor);
begin
   FColumns.Add(Pointer(Fdesc));
   inherited AddCol;
end;

function TDataTable.AddRow: TDataTableRow;
begin
  inherited AddRow();
  Result:=Rows[RowCount-1];
end;

procedure TDataTable.InsertCol(Fdesc: TFieldDescriptor; Index: Integer);
begin
 if (Index > FColumns.Count-1)  or (Index<0) then Raise EDynArray.Create('Index out of range');
 AddCol(Fdesc);
 MoveColRow(true,ColCount-1,Index,False);
 FColumns.Move(FColumns.Count-1,Index);
end;

function TDataTable.ColIndex(AColName: String): Integer;
begin
  Result:=FColumns.Index(AColName);
end;

function TDataTable.ColName(AColIndex: Integer): string;
begin
  Result:=FColumns.Name(AColIndex);
end;

procedure TDataTable.DelCol(Index: Integer);
begin
 if Index > FColumns.Count-1 then Raise EDynArray.Create('Index out of range');
 FColumns.Delete(Index);
 inherited DelCol(Index);
end;

procedure TDataTable.AddCol;
begin
  AddCol;
end;

procedure TDataTable.InsertCol(Index: Integer);
begin

  inherited InsertCol(Index);
end;

procedure TDataTable.Clear;
var
  i: Integer;
begin
 for i:=0 to FColumns.Count-1 do
    begin
      if FColumns[i] <> nil then
        TFieldDescriptor(FColumns[i]).Free;
      FColumns.Delete(i);
    end;
 inherited Clear;
end;

procedure TDataTable.GetEditMask(Col: integer; var Value: String);
begin
  if Col<0 then exit;
  if Col>FColumns.Count-1 then exit;

    Value:=TFieldDescriptor(FColumns[Col]).EditMask;
end;


